/*
 *  Copyright (C) 2011 in-somnia
 * 
 *  This file is part of JAAD.
 * 
 *  JAAD is free software; you can redistribute it and/or modify it 
 *  under the terms of the GNU Lesser General Public License as 
 *  published by the Free Software Foundation; either version 3 of the 
 *  License, or (at your option) any later version.
 *
 *  JAAD is distributed in the hope that it will be useful, but WITHOUT 
 *  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 *  or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General 
 *  Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library.
 *  If not, see <http://www.gnu.org/licenses/>.
 */
package net.sourceforge.jaad.aac.filterbank;

import net.sourceforge.jaad.aac.AACException;
import net.sourceforge.jaad.aac.syntax.Constants;
import net.sourceforge.jaad.aac.syntax.ICSInfo.WindowSequence;

public class FilterBank implements Constants, SineWindows, KBDWindows {

	private final float[][] LONG_WINDOWS;// = {SINE_LONG, KBD_LONG};
	private final float[][] SHORT_WINDOWS;// = {SINE_SHORT, KBD_SHORT};
	private final int length, shortLen, mid, trans;
	private final MDCT mdctShort, mdctLong;
	private final float[] buf;
	private final float[][] overlaps;

	public FilterBank(boolean smallFrames, int channels) throws AACException {
		if(smallFrames) {
			length = WINDOW_SMALL_LEN_LONG;
			shortLen = WINDOW_SMALL_LEN_SHORT;
			LONG_WINDOWS = new float[][]{SINE_960, KBD_960};
			SHORT_WINDOWS = new float[][]{SINE_120, KBD_120};
		}
		else {
			length = WINDOW_LEN_LONG;
			shortLen = WINDOW_LEN_SHORT;
			LONG_WINDOWS = new float[][]{SINE_1024, KBD_1024};
			SHORT_WINDOWS = new float[][]{SINE_128, KBD_128};
		}
		mid = (length-shortLen)/2;
		trans = shortLen/2;

		mdctShort = new MDCT(shortLen*2);
		mdctLong = new MDCT(length*2);

		overlaps = new float[channels][length];
		buf = new float[2*length];
	}

	public void process(WindowSequence windowSequence, int windowShape, int windowShapePrev, float[] in, float[] out, int channel) {
		int i;
		float[] overlap = overlaps[channel];
		switch(windowSequence) {
			case ONLY_LONG_SEQUENCE:
				mdctLong.process(in, 0, buf, 0);
				//add second half output of previous frame to windowed output of current frame
				for(i = 0; i<length; i++) {
					out[i] = overlap[i]+(buf[i]*LONG_WINDOWS[windowShapePrev][i]);
				}

				//window the second half and save as overlap for next frame
				for(i = 0; i<length; i++) {
					overlap[i] = buf[length+i]*LONG_WINDOWS[windowShape][length-1-i];
				}
				break;
			case LONG_START_SEQUENCE:
				mdctLong.process(in, 0, buf, 0);
				//add second half output of previous frame to windowed output of current frame
				for(i = 0; i<length; i++) {
					out[i] = overlap[i]+(buf[i]*LONG_WINDOWS[windowShapePrev][i]);
				}

				//window the second half and save as overlap for next frame
				for(i = 0; i<mid; i++) {
					overlap[i] = buf[length+i];
				}
				for(i = 0; i<shortLen; i++) {
					overlap[mid+i] = buf[length+mid+i]*SHORT_WINDOWS[windowShape][shortLen-i-1];
				}
				for(i = 0; i<mid; i++) {
					overlap[mid+shortLen+i] = 0;
				}
				break;
			case EIGHT_SHORT_SEQUENCE:
				for(i = 0; i<8; i++) {
					mdctShort.process(in, i*shortLen, buf, 2*i*shortLen);
				}

				//add second half output of previous frame to windowed output of current frame
				for(i = 0; i<mid; i++) {
					out[i] = overlap[i];
				}
				for(i = 0; i<shortLen; i++) {
					out[mid+i] = overlap[mid+i]+(buf[i]*SHORT_WINDOWS[windowShapePrev][i]);
					out[mid+1*shortLen+i] = overlap[mid+shortLen*1+i]+(buf[shortLen*1+i]*SHORT_WINDOWS[windowShape][shortLen-1-i])+(buf[shortLen*2+i]*SHORT_WINDOWS[windowShape][i]);
					out[mid+2*shortLen+i] = overlap[mid+shortLen*2+i]+(buf[shortLen*3+i]*SHORT_WINDOWS[windowShape][shortLen-1-i])+(buf[shortLen*4+i]*SHORT_WINDOWS[windowShape][i]);
					out[mid+3*shortLen+i] = overlap[mid+shortLen*3+i]+(buf[shortLen*5+i]*SHORT_WINDOWS[windowShape][shortLen-1-i])+(buf[shortLen*6+i]*SHORT_WINDOWS[windowShape][i]);
					if(i<trans) out[mid+4*shortLen+i] = overlap[mid+shortLen*4+i]+(buf[shortLen*7+i]*SHORT_WINDOWS[windowShape][shortLen-1-i])+(buf[shortLen*8+i]*SHORT_WINDOWS[windowShape][i]);
				}

				//window the second half and save as overlap for next frame
				for(i = 0; i<shortLen; i++) {
					if(i>=trans) overlap[mid+4*shortLen+i-length] = (buf[shortLen*7+i]*SHORT_WINDOWS[windowShape][shortLen-1-i])+(buf[shortLen*8+i]*SHORT_WINDOWS[windowShape][i]);
					overlap[mid+5*shortLen+i-length] = (buf[shortLen*9+i]*SHORT_WINDOWS[windowShape][shortLen-1-i])+(buf[shortLen*10+i]*SHORT_WINDOWS[windowShape][i]);
					overlap[mid+6*shortLen+i-length] = (buf[shortLen*11+i]*SHORT_WINDOWS[windowShape][shortLen-1-i])+(buf[shortLen*12+i]*SHORT_WINDOWS[windowShape][i]);
					overlap[mid+7*shortLen+i-length] = (buf[shortLen*13+i]*SHORT_WINDOWS[windowShape][shortLen-1-i])+(buf[shortLen*14+i]*SHORT_WINDOWS[windowShape][i]);
					overlap[mid+8*shortLen+i-length] = (buf[shortLen*15+i]*SHORT_WINDOWS[windowShape][shortLen-1-i]);
				}
				for(i = 0; i<mid; i++) {
					overlap[mid+shortLen+i] = 0;
				}
				break;
			case LONG_STOP_SEQUENCE:
				mdctLong.process(in, 0, buf, 0);
				//add second half output of previous frame to windowed output of current frame
				//construct first half window using padding with 1's and 0's
				for(i = 0; i<mid; i++) {
					out[i] = overlap[i];
				}
				for(i = 0; i<shortLen; i++) {
					out[mid+i] = overlap[mid+i]+(buf[mid+i]*SHORT_WINDOWS[windowShapePrev][i]);
				}
				for(i = 0; i<mid; i++) {
					out[mid+shortLen+i] = overlap[mid+shortLen+i]+buf[mid+shortLen+i];
				}
				//window the second half and save as overlap for next frame
				for(i = 0; i<length; i++) {
					overlap[i] = buf[length+i]*LONG_WINDOWS[windowShape][length-1-i];
				}
				break;
		}
	}

	//only for LTP: no overlapping, no short blocks
	public void processLTP(WindowSequence windowSequence, int windowShape, int windowShapePrev, float[] in, float[] out) {
		int i;

		switch(windowSequence) {
			case ONLY_LONG_SEQUENCE:
				for(i = length-1; i>=0; i--) {
					buf[i] = in[i]*LONG_WINDOWS[windowShapePrev][i];
					buf[i+length] = in[i+length]*LONG_WINDOWS[windowShape][length-1-i];
				}
				break;

			case LONG_START_SEQUENCE:
				for(i = 0; i<length; i++) {
					buf[i] = in[i]*LONG_WINDOWS[windowShapePrev][i];
				}
				for(i = 0; i<mid; i++) {
					buf[i+length] = in[i+length];
				}
				for(i = 0; i<shortLen; i++) {
					buf[i+length+mid] = in[i+length+mid]*SHORT_WINDOWS[windowShape][shortLen-1-i];
				}
				for(i = 0; i<mid; i++) {
					buf[i+length+mid+shortLen] = 0;
				}
				break;

			case LONG_STOP_SEQUENCE:
				for(i = 0; i<mid; i++) {
					buf[i] = 0;
				}
				for(i = 0; i<shortLen; i++) {
					buf[i+mid] = in[i+mid]*SHORT_WINDOWS[windowShapePrev][i];
				}
				for(i = 0; i<mid; i++) {
					buf[i+mid+shortLen] = in[i+mid+shortLen];
				}
				for(i = 0; i<length; i++) {
					buf[i+length] = in[i+length]*LONG_WINDOWS[windowShape][length-1-i];
				}
				break;
		}
		mdctLong.processForward(buf, out);
	}

	public float[] getOverlap(int channel) {
		return overlaps[channel];
	}
}
